#include <string.h>
#include <openssl/evp.h>
#include <openssl/crypto.h>
#include <vector>
#include <utils.cpp>
#include <error.cpp>
using namespace std;

static void cb_cipher_names_len(const EVP_CIPHER *cipher, const char *from,
	const char *to, void *x) {
	if (cipher) {
		*((int *)x) += strlen(EVP_CIPHER_name(cipher)) + 1;
	}
}

static void cb_cipher_names(const EVP_CIPHER *cipher, const char *from,
	const char *to, void *x) {
	if (cipher) {
		if (strlen((char *)x) > 0) {
			strcat((char *)x, ":");
		}
		strcat((char *)x, EVP_CIPHER_name(cipher));
	}
}

static char *get_cipherNames(void) {
	char *ret = NULL;
	int len = 0;
	EVP_CIPHER_do_all_sorted(cb_cipher_names_len, &len);
	if (!(ret = (char*)OPENSSL_zalloc(len))) {
		return NULL;
	}
	EVP_CIPHER_do_all_sorted(cb_cipher_names, ret);
	return ret;
}

extern void _OPENSSL_free(void *addr);

void get_cipher_names(vector<string>& ciphers)
{
    auto names = get_cipherNames();
    const char* s = ":";
    split_vector( ciphers,names, s);
    _OPENSSL_free(names);
}

int get_cipher_key_length(const char* name)
{
    auto cipher = EVP_get_cipherbyname(name);
    if ( cipher == nullptr)
    {
        throw get_errors();
    }

    return int(EVP_CIPHER_key_length(cipher));
}

int get_cipher_block_size(const char* name)
{
    auto cipher = EVP_get_cipherbyname(name);
    if ( cipher == nullptr)
    {
        throw get_errors();
    }

    return int(EVP_CIPHER_block_size(cipher));
}

int get_cipher_iv_length(const char* name)
{
    auto cipher = EVP_get_cipherbyname(name);
    if ( cipher == nullptr)
    {
        throw get_errors();
    }
    return int(EVP_CIPHER_iv_length(cipher));
}

EVP_CIPHER_CTX* new_cipher_context(const char* name, const unsigned char* key, const unsigned char* iv, bool encrypt )
{
    auto cipher = EVP_get_cipherbyname(name);

    if( cipher == nullptr)
    {
        throw get_errors();
    }

    if( sizeof(key) == 0)
    {
        throw "no key";
    }
    
    // if ( sizeof(key) != int(EVP_CIPHER_key_length(cipher)))
    // {
    //     throw " Invalid key length";
    // }

    // if ( 0 != EVP_CIPHER_iv_length(cipher))
    // {
    //     if ( sizeof(iv) == 0)
    //     {
    //         throw "No IV";
    //     }
    //     if( sizeof(iv) != int(EVP_CIPHER_iv_length(cipher)) )
    //     {
    //         throw "Invalid IV";
    //     }
    // }

    auto ctx = EVP_CIPHER_CTX_new();
    if ( ctx == nullptr)
    {
        throw get_errors();
    }

    int enc = encrypt == false ? 0:1;

    if ( 1 != EVP_CipherInit(ctx, cipher, key, iv, enc))
    {
        throw get_errors();
    }
    return ctx;
}


void free_cipher_ctx(EVP_CIPHER_CTX* ctx)
{
   EVP_CIPHER_CTX_free(ctx);
}

void update_cipher_ctx(EVP_CIPHER_CTX* ctx, const unsigned char* in, int inlen ,unsigned char* out, int outlen)
{
    if ( 1 != EVP_CipherUpdate(ctx, out, &outlen, in, inlen))
    {
        throw get_errors();
    }
    out[outlen] = 0;
}

int get_ciper_update_out_length(EVP_CIPHER_CTX* ctx, const unsigned char* in, int inlen)
{
    return inlen+EVP_CIPHER_CTX_block_size(ctx);
}
int get_cipher_final_block_size(EVP_CIPHER_CTX* ctx)
{
    return int(EVP_CIPHER_CTX_block_size(ctx));
}

void final_cipher_ctx(EVP_CIPHER_CTX* ctx, unsigned char* out, int& outlen)
{
    if ( 1 != EVP_CipherFinal(ctx, out, &outlen))
    {
        throw get_errors();
    }
    out[outlen] = 0;
}